#include "Parse.h"
#include "Exception.hpp"

namespace cal
{

using namespace header;

void Parse::parse(std::istream& is, std::ostream& os, bool reserve_last) 
{
    if (reserve_last == false) _vt.clear();
    TokenStream ts(is);
    while (!_quit) 
    try {
        if (&is == &std::cin || &os == &std::cout) os << "> ";
        parse_command_line(ts, os);
    }
    catch (const Exception& e) {
        clear_mess(ts);
        os << "~ " << e.what() << "\n";
    }
}

void Parse::parse_command_line(TokenStream& ts, std::ostream& os)
{
    switch (ts.peek().kind) {
        case tok::eop: case tok::eof:
        {
            _quit = true;
            break;
        }
        case tok::eocl: 
        {
            ts.get();
            break;
        }
        case tok::kw_set:
        {
            parse_set(ts);
            os << get_keyword_spelling(tok::kw_set) << " " << _vt.added_assignment() << " successfullly.\n";
            break;
        }
        case tok::kw_list:
        {
            parse_list(ts);
            _vt.print(os);
            os << "\n";
            break;
        }
        case tok::identifier:
        {
            auto id = ts.get();
            if (ts.peek().kind == tok::equ) {
                ts.putback(id);
                parse_assignment_stmt(ts);
                os << "the assignment (" << _vt.added_assignment() << ") is successful\n";
                break;
            }
            ts.putback(id);
            parse_expr(ts);
            os << get_operator_spelling(tok::equ) << " " << _res << "\n";
            break;
        }
        case tok::number: case tok::l_bracket: case tok::add: case tok::sub: 
        {
            parse_expr(ts);
            os << get_operator_spelling(tok::equ) << " " << _res << "\n";
            break;
        }
        default: throw Exception{"bad command-line"};
    }
}

void Parse::clear_mess(TokenStream& ts)
{
    while (ts.peek().kind != tok::eocl) {
        ts.get();
        if (ts.eof()) return;
    }
    ts.get();
}

header::Number Parse::get_value(const Identifier& id)
{
    if (!_vt.exist(id))
        throw Exception{"undefined variable: " + id.str()};
    return _vt.get_value(id);
}

void Parse::parse_set(TokenStream& ts) 
{
    ts.get();   // 'set'
    if (ts.peek().kind != tok::identifier) 
        throw Exception{"lack of identifier"};
    parse_assignment_stmt(ts, true);
}

void Parse::parse_assignment_stmt(TokenStream& ts, bool check_redefined)
{
    std::string var = ts.get().value;
    if (ts.peek().kind != tok::equ) 
        THROW_LACK_OF_OPERATOR(tok::equ);
    ts.get();
    if (ts.peek().kind != tok::number) 
        throw Exception{"lack of number"};
    Number val = ts.get().value;
    if (ts.peek().kind != tok::eocl) 
        THROW_LACK_OF_OPERATOR(tok::eocl);
    // 1
    if (check_redefined) {
        if (_vt.exist(var)) throw Exception{"redefined variable: " + var};
    }
    else {
        if (!_vt.exist(var)) throw Exception{"undefined variable: " + var};
    }
    // 2
    ts.get();
    // 1  2 未知不能反过来
    _vt.add(var, val);
}

void Parse::parse_list(TokenStream& ts)
{
    ts.get();
    if (ts.peek().kind != tok::eocl) 
        THROW_LACK_OF_OPERATOR(tok::eocl);
    ts.get();
}

void Parse::parse_expr(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::l_bracket: case tok::add: case tok::sub: 
        case tok::number: case tok::identifier:
        {
            parse_term(ts);
            parse_E(ts);
            break;
        }
        default: throw Exception{"bad expression"};
    }
    if (ts.peek().kind != tok::eocl) 
        THROW_LACK_OF_OPERATOR(tok::eocl);
    ts.get();
}

void Parse::parse_E(TokenStream& ts) 
{
    Number tmp = _res;
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::add:
        {   
            ts.get();
            parse_term(ts);
            _res = tmp + _res;
            parse_E(ts);
            break;
        }
        case tok::sub:
        {
            ts.get();
            parse_term(ts);
            _res = tmp - _res;
            parse_E(ts);
            break;
        }
        case tok::r_bracket: case tok::eocl: break;
        default: throw Exception{"lack of operator"};
    }
}

void Parse::parse_term(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::l_bracket: case tok::add: case tok::sub: 
        case tok::number: case tok::identifier:
        {
            parse_factor(ts);
            parse_T(ts);
            break;
        }
        default: throw Exception{"bad term"};
    }
}

void Parse::parse_T(TokenStream& ts) 
{
    Number tmp = _res;
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::mul:
        {
            ts.get();
            parse_factor(ts);
            _res = tmp * _res;
            parse_T(ts);
            break;
        }
        case tok::div:
        {
            ts.get();
            parse_factor(ts);
            _res = tmp / _res;
            parse_T(ts);
            break;
        }
        case tok::mod:
        {
            ts.get();
            parse_factor(ts);
            _res = tmp % _res;
            parse_T(ts);
            break;
        }
        case tok::r_bracket: case tok::add: 
        case tok::sub: case tok::eocl: break;
        default: throw Exception{"lack of operator"};
    }
}

void Parse::parse_factor(TokenStream& ts) 
{
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::l_bracket: case tok::add: case tok::sub: 
        case tok::number: case tok::identifier:
        {
            parse_primary(ts);
            parse_F(ts);
            break;
        }
        default: throw Exception{"bad factor"};
    }
}

void Parse::parse_F(TokenStream& ts) 
{
    Number tmp = _res;
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::pow:
        {
            ts.get();
            parse_factor(ts);
            _res = tmp ^ _res;
            break;
        }
        case tok::add: case tok::sub: case tok::mul: case tok::div:
        case tok::mod: case tok::r_bracket: case tok::eocl: break;
        default: throw Exception{"lack of operator"};
    }
}

void Parse::parse_primary(TokenStream& ts) 
{
    Number tmp = _res;
    auto peek = ts.peek();
    switch (peek.kind) {
        case tok::l_bracket:
        {
            ts.get();
            parse_expr(ts);
            if (ts.peek().kind != tok::r_bracket) 
                THROW_LACK_OF_OPERATOR(tok::r_bracket);
            ts.get();
            break;
        }
        case tok::add:
        {
            ts.get();
            if (ts.peek().kind == tok::number) _res = Number{ts.get().value};
            else if (ts.peek().kind == tok::identifier) _res = get_value(ts.get().value);
            else throw Exception{"lack of number"};
            break;
        }
        case tok::sub:
        {
            ts.get();
            if (ts.peek().kind == tok::number) _res = Number{ts.get().value} * "-1";
            else if (ts.peek().kind == tok::identifier) _res = get_value(ts.get().value) * "-1";
            else throw Exception{"lack of number"};
            break;
        }
        case tok::number:
        {
            _res = ts.get().value;
            break;
        }
        case tok::identifier:
        {
            _res = get_value(ts.get().value);
            break;
        }
        default: throw Exception{"bad primary"};
    }
}

}   // namespace cal
